﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Linq;
using System.Windows;
using WikiMarkupConverter.RegexModels;

namespace WikiMarkupConverter
{
    /// <summary>
    /// Provides the necessary methods to convert one or multiple files from one syntax to another
    /// </summary>
    internal class SyntaxConverter
    {

        private readonly Syntax _origin;
        private readonly Syntax _target;

        public SyntaxConverter(Syntax originalSyntax, Syntax targetSyntax)
        {
            _origin = originalSyntax;
            _target = targetSyntax;
        }

        /// <summary>
        /// Converts a single file from one markup to another
        /// </summary>
        /// <param name="filePath">Path of the file to convert</param>
        /// <param name="destinationFile">Location of the converted file</param>
        /// <returns></returns>
        private async Task ConvertFileAsync(string filePath, string destinationFile)
        {
            if (!File.Exists(filePath))
                return;

            var lines = File.ReadAllLines(filePath);

            lines = await ConvertInLineMarkups(lines);
            lines = await ConvertMultiLineMarkups(lines);

            var directoryInfo = new FileInfo(destinationFile).Directory;
            if (directoryInfo != null)
            {
                Directory.CreateDirectory(directoryInfo.FullName);

                await Task.Run(() => File.WriteAllLines(destinationFile, lines));
            }
        }

        /// <summary>
        /// Converts all files from input directory into the corresponding markup language.
        /// </summary>
        /// <param name="inputPath">Path for the directory or file to convert</param>
        /// <param name="outputPath">Path of the output directory</param>
        /// <param name="extensions">List of all the file extensions that will be converted</param>
        internal async Task ProcessConvertAsync(string inputPath, string outputPath, List<string> extensions)
        {
            // Checking Origin and Target syntaxes
            if (_origin == null || _target == null || _origin.Name == _target.Name)
            {
                MessageBox.Show("You must select different origin and target syntaxes (i.e. DokuWiki To MarkDown).",
                                "Invalid convertion", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            // Checking Input and Output directory
            inputPath = inputPath.Replace("\"", "");
            outputPath = outputPath.Replace("\"", "");

            if (!outputPath.EndsWith("\\"))
                outputPath += "\\";

            if (string.IsNullOrWhiteSpace(inputPath) || string.IsNullOrWhiteSpace(outputPath) ||
                !Directory.Exists(inputPath))
            {
                MessageBox.Show("You must select a valid input and output path.",
                "Invalid convertion", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                return;
            }

            var files = await FileHelper.GetFilesAsync(inputPath, true, extensions);

            if (MessageBox.Show(string.Format("You are about to convert {0} files, proceed?", files.Count),
                                "Please confirm your convertion", MessageBoxButton.YesNo) == MessageBoxResult.No)
                return;

            var tasks = new List<Task>();
            foreach (var file in files)
            {
                var outputFilePath = Path.Combine(outputPath, file.Replace(inputPath, ""));

                tasks.Add(ConvertFileAsync(file, Path.Combine(outputPath, outputFilePath)));
            }

            await Task.WhenAll(tasks).ContinueWith(delegate
                {
                    LogFileHelper.WriteLogAsync();
                    MessageBox.Show("Convertion successful!");
                });
        }

        private async Task<string[]> ConvertInLineMarkups(string[] lines)
        {
            await Task.Run(() =>
                 {
                     foreach (var markup in _origin.Markups.OfType<SingleLineMarkup>())
                     {
                         try
                         {
                             var targetMarkup = _target.Markups.OfType<SingleLineMarkup>().First(m => m.Name == markup.Name);
                             var currentLineCount = 0;

                             foreach (var line in lines)
                             {
                                 var currentLine = line;
                                 var regexCount = 0;
                                 foreach (var regex in markup.InlineRegexes)
                                 {
                                     SingleLineRegex targetRegex;
                                     if (targetMarkup.InlineRegexes.Count > regexCount)
                                         targetRegex = targetMarkup.InlineRegexes[regexCount];
                                     else
                                         targetRegex = targetMarkup.InlineRegexes.Last();

                                     if (targetRegex != null && !string.IsNullOrEmpty(regex.From))
                                     {
                                         currentLine = Regex.Replace(currentLine, regex.From, targetRegex.To);
                                         lines[currentLineCount] = currentLine;
                                     }
                                     regexCount++;
                                 }
                                 currentLineCount++;
                             }
                         }
                         catch (Exception e)
                         {
                             LogFileHelper.AddMessageToLog(string.Format("Error while converting markup {0} from {1} syntax to {2}",
                                 markup.Name, _origin.Name, _target.Name));
                         }
                     }
                 });
            return lines;
        }

        private async Task<string[]> ConvertMultiLineMarkups(string[] lines)
        {
            await Task.Run(() =>
                {
                    foreach (var markup in _origin.Markups.OfType<MultiLineMarkup>())
                    {
                        var targetMarkup = _target.Markups.OfType<MultiLineMarkup>().First(m => m.Name == markup.Name);
                        var regexCount = 0;
                        foreach (var regex in markup.MultiLineRegexes)
                        {
                            var currentLineCount = 0;
                            var isInsideTag = false;

                            // Getting target regex
                            MultiLineRegex targetRegex;
                            if (targetMarkup.InlineRegexes.Count > regexCount)
                                targetRegex = targetMarkup.MultiLineRegexes[regexCount];
                            else
                                targetRegex = targetMarkup.MultiLineRegexes.Last();

                            foreach (var line in lines)
                            {
                                var previousIsInsideTag = isInsideTag;
                                // Define mode (normal text or in a multi-lined tag)
                                isInsideTag = isInsideTag ? !Regex.IsMatch(line, regex.FromClosingTag) : Regex.IsMatch(line, regex.FromOpeningTag);

                                // Define position (starting tag, ending tag, inside, outside)
                                // Position: Opening tag
                                if (!previousIsInsideTag && isInsideTag)
                                    lines[currentLineCount] = Regex.Replace(line, regex.FromOpeningTag, targetRegex.ToOpeningTag);
                                // Position: Inside
                                else if (previousIsInsideTag && isInsideTag)
                                    lines[currentLineCount] = Regex.Replace(line, regex.FromInsideLine,
                                                                           targetRegex.ToInsideLine);
                                // Position: Closing tag
                                else if (previousIsInsideTag)
                                    lines[currentLineCount] = Regex.Replace(line, regex.FromClosingTag,
                                                                           targetRegex.ToClosingTag);

                                currentLineCount++;
                            }
                            regexCount++;
                        }


                    }
                });
            return lines;
        }
    }
}
